/**
 * FeatureWarpingFilter.java
 * 
 * @date: 2012-03-09
 * @author: Xiaoyu Guo (MUN# 200982155)
 * This file is part of the course project for COMP 6752, 
 * Application of Computer Graphics. All the file is written
 * by myself.
 */
package mun.cs6752.project.filter.featureWarping;

import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import mun.cs6752.project.filter.abstractClasses.AbstractImageFilter;
import mun.cs6752.project.filter.abstractClasses.AbstractImageFilterControl;
import mun.cs6752.project.filter.featureWarping.FeatureWarpingParameter.LineSegment;
import mun.cs6752.project.filter.interfaces.IImageFilterParameter;
import mun.cs6752.project.filter.interfaces.IImageProvider;
import mun.cs6752.project.model.PixelAccumulator;
import mun.cs6752.project.model.Vector2;
import mun.cs6752.project.ui.ImageCanvas;

/**
 * @author Xiaoyu Guo
 *
 */
public class FeatureWarpingFilter extends AbstractImageFilter {

    private static final double WEIGHTED_AVERAGE_CONSTANT_A = 0.0001;
    private static final double WEIGHTED_AVERAGE_CONSTANT_B = 1.0;
    private static final double WEIGHTED_AVERAGE_CONSTANT_C = 2.0;

    /**
     * @param provider
     * @param name
     */
    public FeatureWarpingFilter(IImageProvider provider) {
        super(provider, "Feature-Based Warping");
    }
    
    @Override
    public boolean isOverridingSourceImageControl(){
        return true;
    }
    
    @Override
    public boolean isOverridingTargetImageControl(){
        return true;
    }

    /* (non-Javadoc)
     * @see mun.cs6752.project.filter.interfaces.IImageFilter#getSourceImageControl(java.awt.image.BufferedImage)
     */
    @Override
    public ImageCanvas getSourceImageControl(BufferedImage srcImage){
        return new ImageViewerWithControlLineSegments(srcImage);
    }

    /* (non-Javadoc)
     * @see mun.cs6752.project.filter.interfaces.IImageFilter#getTargetImageControl()
     */
    @Override
    public ImageCanvas getTargetImageControl(){
        return new ImageViewerWithControlLineSegments();
    }
    
    /* (non-Javadoc)
     * @see mun.cs6752.project.filter.interfaces.IImageFilter#execute(java.awt.image.BufferedImage, mun.cs6752.project.filter.interfaces.IImageFilterParameter)
     */
    @Override
    public BufferedImage execute(BufferedImage srcImage, IImageFilterParameter param) {
        FeatureWarpingParameter p = null;
        if(param == null || !(param instanceof FeatureWarpingParameter)){
            p = FeatureWarpingParameter.getDefault();
        } else {
            p = (FeatureWarpingParameter) param;
        }
        
        BufferedImage destImage = new BufferedImage(
            srcImage.getWidth(), 
            srcImage.getHeight(), 
            BufferedImage.TYPE_INT_RGB
        );
        
        /* algorithm:
         * for all pixels, calculate all local coordinates
         * and calculate the global coordinate in the dest image. 
         */
        PixelAccumulator[][] pixels = new PixelAccumulator[srcImage.getHeight()][srcImage.getWidth()];
        
        for(int y = 0; y < srcImage.getHeight(); y++){
            pixels[y] = new PixelAccumulator[srcImage.getWidth()];
            for(int x = 0; x < srcImage.getWidth(); x++){
                pixels[y][x] = new PixelAccumulator();
            }
        }
        
        for(int y = 0; y < srcImage.getHeight(); y++){
            for(int x = 0; x < srcImage.getWidth(); x++){
                // calculate all local coordinates
                List<Vector2> localCoord = new ArrayList<Vector2>();
                List<Double> distances = new ArrayList<Double>();
                List<Double> lineLengths = new ArrayList<Double>();
                for(int i = 0; i < p.getSrcLines().size(); i++){
                    LineSegment l = p.getSrcLines().get(i);
                    Vector2 a = l.getStartPoint();
                    Vector2 b = l.getEndPoint();
                    Vector2 p_a = new Vector2(x - a.getX(), y - a.getY());
                    Vector2 b_a = b.minus(a);
                    
                    double u, v;
                    // u = (P-A) dot (B-A) / (|AB|^2)
                    u = p_a.dotProduct(b_a);
                    u /= Math.pow(b_a.getLength(), 2);
                    
                    // v = (P-A) dot D
                    // where D = (By-Ay, Ax-Bx)
                    Vector2 d = new Vector2(b.getY() - a.getY(), a.getX() - b.getX());
                    v = p_a.dotProduct(d.getUnitVector());
                    
                    // now (u,v) is the local coordinate under line segment no. i
                    
                    // now calculate global coordinate for line segment no.i
                    /*
                     * d_ = (B_y - A_y, A_x - B_x).unit
                     * p_ = A_ + u * (B_ - A_) + v * d_
                     */
                    
                    l = p.getDestLines().get(i);
                    a = l.getStartPoint();
                    b = l.getEndPoint();
                    
                    d = new Vector2(b.getY() - a.getY(), a.getX() - b.getX());
                    Vector2 p_ = a;
                    b_a = b.minus(a);
                    p_ = p_.add(d.multiply(v));
                    p_ = p_.add(b_a).multiply(u);
                    
                    // now p_ = (x_, y_)
                    localCoord.add(p_);
                    
                    double distance = 0;
                    if(p_.getX() < 0){
                        distance = p_.minus(a).getLength();
                    } else if (p_.getX() > 1){
                        distance = p_.minus(b).getLength();
                    } else {
                        distance = Math.abs(p_.getY());
                    }
                    distances.add(distance);
                    
                    lineLengths.add(b_a.getLength());
                }
                
                // calculate the global coordinate
                Vector2 accumulator = new Vector2();
                double total_w = 0;
                for(int i = 0; i < localCoord.size(); i++){
                    Vector2 p_ = localCoord.get(i);
                    /* w = ((li^b) / (a + di)) ^ c
                     * where li = length of control line segment
                     * di = distance
                     */
                    double w = Math.pow((Math.pow(lineLengths.get(i), WEIGHTED_AVERAGE_CONSTANT_B) / (WEIGHTED_AVERAGE_CONSTANT_A + distances.get(i))), WEIGHTED_AVERAGE_CONSTANT_C);
                    accumulator = accumulator.add(p_.multiply(w));
                }
                accumulator = accumulator.multiply(1.0 / total_w);
                int accx = (int) Math.round(accumulator.getX());
                int accy = (int) Math.round(accumulator.getY());
                if(accx < 0 || accx >= srcImage.getWidth()){
                    continue;
                }
                if(accy < 0 || accy >= srcImage.getHeight()){
                    continue;
                }
                pixels[accy][accx].add(srcImage.getRGB(x, y));
            }
        }
        
        // go through all previously set pixels 
        for(int y = 0; y < srcImage.getHeight(); y++){
            for(int x = 0; x < srcImage.getWidth(); x++){
                destImage.setRGB(x, y, pixels[y][x].getRGB());
            }
        }
        
        return destImage;
    }

    /* (non-Javadoc)
     * @see mun.cs6752.project.filter.interfaces.IImageFilter#getControl()
     */
    @Override
    public AbstractImageFilterControl getControl() {
        return new FeatureWarpingFilterControl(provider, this);
    }

}
